外键(一对多


1. 使用 ForeignKey 创建外键

  • 在建立外键(即: 一对多关系)的时候,ForeignKey() 一般定义在多的一方(即: 一对多关系中多的一方的表类中)

  • models.ForeignKey(to='表的类名', to_field='字段名', related_name='反向操作使用的属性名')

  • ForeignKey 的参数解释:

    • to -> 设置需要关联表的类名
      • to='Classes' -> 加上引号是为了在当前models.py文件中通过反射去查询表的类
      • to=Classes -> 不加引号,直接使用表的类,如果Classes类定义在to的表类下面那么就会报错,因为Classes未定义就引用了
                        -> 不加引号的使用场景: 使用导入的表类时无需加引号(不会出现上面的报错情况,因为表类在文件一开始就已经导入了)
    • to_field -> 设置关联表的那个字段,如果不传默认关联 id
    • related_name -> 反向操作时,使用的属性名,用于代替原反向查询时的 '表名_set' 的写法 -> (通俗理解: 给主表起的别名,用于反向操作)
    • related_query_name -> 反向查询操作时,使用的连接前缀,用于替换表名
    • on_delete -> 当删除关联表中的数据时,当前表与其关联的行的行为

# models.ForeignKey(to='表的类名', to_field='字段名', related_name='反向操作使用的属性名')

# 班级表
class Classes(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=10)


# 学生表
class Student(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=10)
    age = models.IntegerField()
    classes = models.ForeignKey(to='Classes', related_name='fkclass')  # 建立外键,且 classes 就是外键属性

8.on_delete -> 外键方法参数

  • 当删除关联表中的数据时,当前表与其关联的行的行为
  • Django1+ 中默认设置了 on_delete=models.CASCADE
  • 注意: 在Django2+中创建外键时一定要设置该参数,不然就会报错

class Student(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=10)
    classes = models.ForeignKey(to='Classes', on_delete=models.CASCADE)

  • on_delete 的值: 
    • models.CASCADE ->  删除关联数据,与之关联也删除
    • models.DO_NOTHING -> 删除关联数据,引发错误IntegrityError
    • models.PROTECT -> 删除关联数据,引发错误ProtectedError
    • models.SET_NULL -> 删除关联数据,与之关联的值设置为null(前提FK字段需要设置为可空)
    • models.SET_DEFAULT -> 删除关联数据,与之关联的值设置为默认值(前提FK字段需要设置默认值)
    • models.SET -> 删除关联数据
      • 与之关联的值设置为指定值,设置:models.SET(值)
      • 与之关联的值设置为可执行对象的返回值,设置:models.SET(可执行对象)

# models.SET 的演示

def func():
    return 10

class MyModel(models.Model):
    user = models.ForeignKey(
        to="User",
        to_field="id",
        on_delete=models.SET(func)
    )

9.db_constraint -> 外键方法参数

  • 是否在数据库中创建外键约束,默认为True
  • 通俗理解: 如果 db_constraint=False ,虽然通过 ForeignKey 字段类建立了索引,但是实际上在数据库中是没有建立,只是说明了该字段有外键的功能,一般用于软外键
  • 软外键的说明: 不在表中建立外键约束,从代码层面上建立外键约束

class Student(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=10)
    classes = models.ForeignKey(to='Classes', db_constraint=True)

外键字段相关


1. 使用外键字段的注意事项

  • 使用 orm 所创建的外键都会在外键字段名的结尾处加上 _id,除非使用了 db_column='xxx' 去重新设置该外键字段的字段名(如果使用 db_column 重新设置外键字段名,不要和类中的外键属性名重复,因为该属性名是用于正向查询的(即:存放着与主表数据相关的从表数据的对象))


  • 使用数据库表中的外键名(xxx_id)去对外键字段中的数据进行添加和修改操作(查询、删除、批量修改除外),而不是直接使用类中的外键字段名,因为类中的外键字段名是用于正向查询(即:存放着与主表数据相关的从表数据的对象)

# 正确示范 -> 这里使用了上面的表的类做示范

# 增加数据
models.Student.objects.create(name='Kevin', classes_id=1)

# 查询数据
models.Student.objects.get(classes_id=1)
models.Student.objects.get(classes=1)

# 删除数据
models.Student.objects.get(classes_id=1).delete()
models.Student.objects.get(classes=1).delete()

# 修改数据
obj = models.Student.objects.get(classes_id=1)
obj = models.Student.objects.get(classes=1)
obj.classes_id= 2
obj.save()

错误示范 -> 这里使用了上面的表的类做示范

# 增加数据
models.Student.objects.create(name='Kevin', classes=1)

# 修改数据
obj = models.Student.objects.get(classes=1)
obj.classes = 2
obj.save()

2. 两种修改外键数据的方法

  • 方法一 -> 通过数据库中的外键名进行修改 -> 推荐使用

# 这里使用了上面的表的类做示范

obj = models.Student.objects.get(classes_id=1)
obj.classes_id= 2
obj.save()

  • 方法二 -> 对类中的外键属性进行重新的赋值

# 这里使用了上面的表的类做示范

class_obj = Classes.objects.get(id=5)
student_obj = Student.objects.get(id=4)
student_obj.classes = class_obj  # 对外键属性进行重新赋值,且该值必须是班级对象,因为 classes 的外键属性返回的就是班级对象
student_obj.save()

# 错误示范: 不要直接修改外键属性所返回的对象,因为直接修改该对象相当于直接修改了从表(班级表)中的内容

student_obj = Student.objects.get(id=4)
student_obj.classes.id = 2
student_obj.classes.save()

3. 外键字段接收的参数

  • 在进行正删改查的时候,如果涉及到外键字段,那么外键字段可以接收两种类型的参数:

    • 对象 -> 外键字段属性名 = 对象 -> 对象一般代指的是查询数据后得到的对象(因为 .外键字段属性名 返回的是一个对象,所以它可以接收一个对象)
    • 数字 -> 外键字段属性名_id = 数字

  • 外键字段接收的参数是 对象 的相关操作

# 增

classes_obj = Classes.objects.filter(id=1).first()
Student.objects.create(name='Yeung', age=18, classes=classes_obj)

# 改

classes = Classes.objects.filter(id=2).first()
student = Student.objects.filter(name='Yeung').first()

student.classes = classes

student.save()

# 查

classes = Classes.objects.filter(id=1).first()
student = Student.objects.filter(classes=classes)

  • 外键字段接收的参数是 数字 的相关操作

# 增 

Student.objects.create(name='Yeung', age=18, classes_id=1)

# 改 

student = Student.objects.filter(name='Yeung').first()

student.classes_id = 1

student.save()

# 查

student = Student.objects.filter(classes_id=1)

  • 在查询数据 或 批量修改外键字段的数据的时候,外键字段可以不用加 _id 直接传递数字类型的参数

    • 查询数据

# classes 是外键字段

student = Student.objects.filter(classes=1)  # 传递的参数是数字类型

# ------------------------------------------------------

classes_obj = Classes.objects.filter(id=1).first()
student = Student.objects.filter(classes=classes_obj)  # 传递的参数是对象

# ------------------------------------------------------

student = Student.objects.get(classes=2)  # 传递的参数是数字类型

# ------------------------------------------------------

classes_obj = Classes.objects.filter(id=2).first()
student = Student.objects.get(classes=classes_obj)  # 传递的参数是对象

    • 批量修改外键字段的数据

# classes 是外键字段

student = Student.objects.filter(classes=1)
student.update(classes=2)  # 传递的参数是数字类型

# ------------------------------------------------------

student = Student.objects.filter(classes=1)
classes_obj = Classes.objects.filter(id=2).first()
student.update(classes=classes_obj)  # 传递的参数是数字类型

查询相关


1. 正向查询 -> 主表获取从表的数据

  • 作用: 简化连表获取数据的操作

  • 正向查询用字段,反向查询用表名

  • 写法一:

    • 通过对象查找
    • 语法: 查询到的对象.类的外键属性 -> 返回值:主表数据相关的从表数据的对象

# Django会将从表的所有数据放进表的类中的外键属性中,当要获取与主表数据相关的从表数据的时候,外键属性就会返回与主表数据相关的从表数据的对象
# 这里使用了上面的表的类做示范

class_obj = Student.objects.get(id=2).classes  # <Classes: Classes object> -> 返回值: 与查询到的 student 数据相关的 class 数据的对象

class_name = Student.objects.get(id=2).classes.name  # '一班'

  • 写法二:

    • 使用 values 或 values_list 通过字段查询

    • 语法:
      • .values('主表字段名', '主表字段名', '主表类的外键属性名__从表字段名', '主表类的外键属性名__主表类的外键属性名下的外键属性名__从表字段名')
      • .values_list('主表字段名', '主表字段名', '主表类的外键属性名__从表字段名', '主表类的外键属性名__主表类的外键属性名下的外键属性名__从表字段名')

student_obj1 = Student.objects.all().values('name', 'classes__id', 'classes__name')  # name是Student表的字段名,而classes__id中的classes是Student类中所创建的外键属性,id则是classes表中的字段
student_obj2 = Student.objects.all().values_list('name', 'classes__id', 'classes__name')

print(student_obj1)  # <QuerySet [{'classes__name': '一班', 'name': 'Kevin', 'classes__id': 1}, {'classes__name': '三班', 'name': 'Aimer', 'classes__id': 4}]>
print(student_obj2)  # <QuerySet [('Kevin', 1, '一班'), ('Aimer', 4, '三班')]>

    • 跨多张表进行查询 -> 查找书名是“西游记”的书的作者的年龄和手机号码和作者详情id(是ORM练习中的最后一题)

author_list = Book.objects.filter(title='西游记').values('author__name', 'author__detail_id', 'author__detail__addr', 'author__detail__email')

2. 反向查询 -> 从表获取主表的数据

  • 作用: 简化连表获取数据的操作

  • 正向查询用字段,反向查询用表名

  • 写法一:

    • 通过对象查找
    • 语法: 查询到的对象.主表的类名_set.all/get/filter() -> 主表类名首字母无需大写

# 这里使用了上面的表的类做示范
# 例子: 查询一班的所有学生

student_list = Classes.objects.get(id=1).student_set.all()  # <QuerySet [<Student: Student object>, <Student: Student object>]>

for student in student_list:
    print(student.name)  # Kevin ……

    • related_name

      • 用于替换 主表的类名_set 的写法 
      • 在表类的外键属性(即: ForeignKey()方法)中添加 related_name='xxx' 即可
      • 如果在表类的外键属性中添加了 related_name='xxx',那么 主表的类名_set 的写法就会无效

# models.py

class Student(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=10)
    classes = models.ForeignKey(to='Classes', related_name='fkclass')

# views.py

student_list = Classes.objects.get(id=1).fkclass.all()  # <QuerySet [<Student: Student object>, <Student: Student object>]>

for student in student_list:
    print(student.name)  # Kevin ……

  • 写法二:

    • 使用 values 或 values_list 通过字段查询

    • 语法:

      • 如果没有设置表类中外键属性的 related_name

        • .values('从表字段名', '从表字段名', '主表类名__主表字段名', '主表类名__主表字段名') -> 主表类名无需大写
        • .values_list('从表字段名', '从表字段名', '主表类名__主表字段名', '主表类名__主表字段名') -> 主表类名无需大写

# models.py

class Student(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=10)
    classes = models.ForeignKey(to='Classes')

student_list1 = Classes.objects.all().values('name', 'student__name')
student_list2 = Classes.objects.all().values_list('name', 'student__name')

print(student_list1)  # <QuerySet [{'student__name': 'Kevin', 'name': '一班'}, {'student__name': 'Aimer', 'name': '三班'}, ……]>
print(student_list2)  # <QuerySet [('一班', 'Kevin'), ('三班', 'Aimer'), ('一班', 'Timmy'), ('二班', None), ('四班', None), ('五班', None)]>

      • 如果设置了表类中外键属性的 related_name

        • .values('从表字段名', '从表字段名', 'related_name所设置的名字__主表字段名', 'related_name所设置的名字__主表字段名')
        • .values_list('从表字段名', '从表字段名', 'related_name所设置的名字__主表字段名', 'related_name所设置的名字__主表字段名')

# models.py

class Student(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=10)
    classes = models.ForeignKey(to='Classes', related_name='fkclass')

student_list1 = Classes.objects.all().values('name', 'fkclass__name')
student_list2 = Classes.objects.all().values_list('name', 'fkclass__name')

print(student_list1)  # <QuerySet [{'fkclass__name': 'Kevin', 'name': '一班'}, {'fkclass__name': 'Aimer', 'name': '三班'}, ……]>
print(student_list2)  # <QuerySet [('一班', 'Kevin'), ('三班', 'Aimer'), ('一班', 'Timmy'), ('二班', None), ('四班', None), ('五班', None)]>

QuerySet方法大全


##################################################################
# PUBLIC METHODS THAT ALTER ATTRIBUTES AND RETURN A NEW QUERYSET #
##################################################################


def all(self)
 # 获取所有的数据对象


def filter(self, *args, **kwargs)
# 条件查询
    # 条件可以是:参数,字典,Q


def exclude(self, *args, **kwargs)
  # 条件查询
    # 条件可以是:参数,字典,Q


def select_related(self, *fields)
 性能相关:表之间进行join连表操作,一次性获取关联的数据。

    总结:
    1. select_related主要针一对一和多对一关系进行优化。
    2. select_related使用SQL的JOIN语句进行优化,通过减少SQL查询的次数来进行优化、提高性能。


def prefetch_related(self, *lookups)
性能相关:多表连表操作时速度会慢,使用其执行多次SQL查询在Python代码中实现连表操作。

    总结:
    1. 对于多对多字段(ManyToManyField)和一对多字段,可以使用prefetch_related()来进行优化。
    2. prefetch_related()的优化方式是分别查询每个表,然后用Python处理他们之间的关系。


def annotate(self, *args, **kwargs)
    # 用于实现聚合group by查询

    from django.db.models import Count, Avg, Max, Min, Sum

    v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id'))
    # SELECT u_id, COUNT(ui) AS `uid` FROM UserInfo GROUP BY u_id

    v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id')).filter(uid__gt=1)
    # SELECT u_id, COUNT(ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1

    v = models.UserInfo.objects.values('u_id').annotate(uid=Count('u_id',distinct=True)).filter(uid__gt=1)
    # SELECT u_id, COUNT( DISTINCT ui_id) AS `uid` FROM UserInfo GROUP BY u_id having count(u_id) > 1


def distinct(self, *field_names)
# 用于distinct去重
    models.UserInfo.objects.values('nid').distinct()
 # select distinct nid from userinfo


    注:只有在PostgreSQL中才能使用distinct进行去重


def order_by(self, *field_names)
 # 用于排序
    models.UserInfo.objects.all().order_by('-id','age')


def extra(self, select=None, where=None, params=None, tables=None, order_by=None, select_params=None)
 # 构造额外的查询条件或者映射,如:子查询
    Entry.objects.extra(select={'new_id': "select col from sometable where othercol > %s"}, select_params=(1,))
    Entry.objects.extra(where=['headline=%s'], params=['Lennon'])
    Entry.objects.extra(where=["foo='a' OR bar = 'a'", "baz = 'a'"])
    Entry.objects.extra(select={'new_id': "select id from tb where id > %s"}, select_params=(1,), order_by=['-nid'])


def reverse(self):
# 倒序
    models.UserInfo.objects.all().order_by('-nid').reverse()
 # 注:如果存在order_by,reverse则是倒序,如果多个排序则一一倒序




def defer(self, *fields):
    models.UserInfo.objects.defer('username','id')
    # 或
models.UserInfo.objects.filter(...).defer('username','id')
    # 映射中排除某列数据


def only(self, *fields):
# 仅取某个表中的数据
models.UserInfo.objects.only('username','id')
    # 或
models.UserInfo.objects.filter(...).only('username','id')


def using(self, alias):
# 指定使用的数据库,参数为别名(setting中的设置)




##################################################
# PUBLIC METHODS THAT RETURN A QUERYSET SUBCLASS #
##################################################


def raw(self, raw_query, params=None, translations=None, using=None):
# 执行原生SQL
    models.UserInfo.objects.raw('select * from userinfo')


 # 如果SQL是其他表时,必须将名字设置为当前UserInfo对象的主键列名
    models.UserInfo.objects.raw('select id as nid from 其他表')


 # 为原生SQL设置参数
    models.UserInfo.objects.raw('select id as nid from userinfo where nid>%s', params=[12,])


# 将获取的到列名转换为指定列名
    name_map = {'first': 'first_name', 'last': 'last_name', 'bd': 'birth_date', 'pk': 'id'}
    Person.objects.raw('SELECT * FROM some_other_table', translations=name_map)


 # 指定数据库
    models.UserInfo.objects.raw('select * from userinfo', using="default")


################### 原生SQL ###################
    from django.db import connection, connections
    cursor = connection.cursor()  # cursor = connections['default'].cursor()
    cursor.execute("""SELECT * from auth_user where id = %s""", [1])
    row = cursor.fetchone() # fetchall()/fetchmany(..)




def values(self, *fields):
    # 获取每行数据为字典格式


def values_list(self, *fields, **kwargs):
# 获取每行数据为元祖


def dates(self, field_name, kind, order='ASC'):
# 根据时间进行某一部分进行去重查找并截取指定内容
    # kind只能是:"year"(年), "month"(年-月), "day"(年-月-日)
    # order只能是:"ASC"  "DESC"
    # 并获取转换后的时间
        - year : 年-01-01
        - month: 年-月-01
        - day  : 年-月-日


    models.DatePlus.objects.dates('ctime','day','DESC')


def datetimes(self, field_name, kind, order='ASC', tzinfo=None):
# 根据时间进行某一部分进行去重查找并截取指定内容,将时间转换为指定时区时间
    # kind只能是 "year", "month", "day", "hour", "minute", "second"
    # order只能是:"ASC"  "DESC"
    # tzinfo时区对象
    models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.UTC)
    models.DDD.objects.datetimes('ctime','hour',tzinfo=pytz.timezone('Asia/Shanghai'))


"""
    pip3 install pytz
    import pytz
    pytz.all_timezones
    pytz.timezone(‘Asia/Shanghai’)
    """


def none(self):
 # 空QuerySet对象




####################################
# METHODS THAT DO DATABASE QUERIES #
####################################


def aggregate(self, *args, **kwargs):
# 聚合函数,获取字典类型聚合结果
   from django.db.models import Count, Avg, Max, Min, Sum
   result = models.UserInfo.objects.aggregate(k=Count('u_id', distinct=True), n=Count('nid'))
===> {'k': 3, 'n': 4}


def count(self):
# 获取个数


def get(self, *args, **kwargs):
 # 获取单个对象


def create(self, **kwargs):
  # 创建对象


def bulk_create(self, objs, batch_size=None):
 # 批量插入
    # batch_size表示一次插入的个数
    objs = [
        models.DDD(name='r11'),
        models.DDD(name='r22')
    ]
    models.DDD.objects.bulk_create(objs, 10)


def get_or_create(self, defaults=None, **kwargs):
# 如果存在,则获取,否则,创建
    # defaults 指定创建时,其他字段的值
    obj, created = models.UserInfo.objects.get_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 2})


def update_or_create(self, defaults=None, **kwargs):
# 如果存在,则更新,否则,创建
    # defaults 指定创建时或更新时的其他字段
    obj, created = models.UserInfo.objects.update_or_create(username='root1', defaults={'email': '1111111','u_id': 2, 't_id': 1})


def first(self):
# 获取第一个


def last(self):
 # 获取最后一个


def in_bulk(self, id_list=None):
# 根据主键ID进行查找
   id_list = [11,21,31]
   models.DDD.objects.in_bulk(id_list)


def delete(self):
# 删除


def update(self, **kwargs):
 # 更新


def exists(self):
# 是否有结果